package com.hc.springbootzookeeper.config;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.List;

public class ZookeeperHelper {
    /**
     * 分布式的应用程序的协调服务
     * -----应用场景------
     * 1.统一命名服务:在分布式的环境下，通常可以对应用程序或者服务器通过一个统一的命名来识别进行访问
     * 2.统一配置管理:在分布式的环境下，配置文件做同步更新操作，可以通过监听来完成
     * 3.节点动态上下线:客户端能够实时的通过Zookeeper获取服务器的状态
     * 4.软负载均衡:Zookeeper会记录每一台服务器访问的次数,让访问最少的服务器去处理最新的请求
     * -----特点-----
     * 1.一个leader和多个follower
     * 2.集群中有半数以上节点存活就能正常工作
     * 3.全局数据一致性,每台服务器都保存一份相同的数据副本
     * 4.数据更新原子性,要么成功,要么失败
     * 5.实时性,一定时间内,客户端能读取到最新数据
     * 6.数据更新顺序执行
     * -----Linux下安装配置步骤-----
     * 1.安装JDK配置环境变量 /etc/profile
     * 2.安装Zookeeper
     *      1)上传安装包至如/opt下
     *      2)解压:tar -xzvf 包名
     *      3)重命名:mv 解压后的文件名 zookeeper
     * 3.配置Zookeeper配置文件
     *      1)进入/opt/zookeeper/conf目录 复制一份模板文件cp zoo.sample.cfg zoo.cfg
     *      2)zoo.cfg配置数据存放的目录:修改dataDir=/opt/zookeeper/zkData
     *      3)zoo.cfg配置日志目录:新增dataLogDir=/opt/zookeeper/zkLog
     *      4)进入/opt/zookeeper目录,创建目录zkData,mkdir zkData
     *      5)进入/opt/zookeeper目录,创建目录zkLog,mkdir zkLog
     * -----基本操作-----
     * 1.启动 ./zkServer.sh start
     * 2.查看启动 jps
     * 3.查看状态 ./zkServer.sh status
     * 4.启动客户端 ./zkCli.sh
     * 5.退出客户端 quit
     * 6.停止 ./zkServer.sh stop
     * -----配置参数解析-----
     * 1.tickTime:客户端与服务端间心跳发送时间间隔
     * 2.initLimit:主节点与从节点之间连接的心跳次数限制
     * 3.syncLimit:主节点请求从节点响应的心跳次数限制
     * 4.dataDir:持久化数据存放目录
     * 5.dataLogDir:日志文件存放目录
     * 6.clientPort:客户端连接服务端的端口
     * 7.admin.serverPort:可修改的服务端默认端口8080
     * -----内部原理-----
     * 1.选举策略
     *      半数机制:集群中半数以上存活,则集群可用
     *      选举过程:(假设5台服务器依次启动)
     *      1)Server1投票给自己 不满足半数 不成为Leader 将票给Server2
     *      2)Server2投票给自己 加上Server1的票 不满足半数 不成为Leader 将票给Server3
     *      3)Server3投票给自己 加上Server2的票 满足半数 成为Leader
     *      4)Server4投票给自己 但无法改变Server3的票数 承认Server3是Leader
     *      5)Server5投票给自己 但无法改变Server3的票数 承认Server3是Leader
     * 2.节点类型
     *      1)持久型:客户端与服务端断开连接后,对应节点上的数据依旧保存
     *      2)短暂型:客户端与服务端断开连接后,对应节点被自动回收
     *      创建节点时会在名称后追加一个单挑递增的数字值,例如Znode001,Znode002,Znode003
     * 3.监听器
     *      1)客户端创建2个线程,一个负责通信,一个负责监听
     *      2)客户端将监听事件发送给服务端,服务端将监听事件进行注册
     *      3)当数据变换时,服务器将此消息发送给监听事件
     *      4)监听事件进行处理
     * 4.写数据流程
     *      1)客户端向Server1上发送写数据请求
     *      2)若Server1不是Leader,则将请求转发给Leader
     *      3)Leader将请求发送给各Server,各Server写成功后将通知Leader
     *      4)当Leader收到半数以上写成功通知,则写入成功
     *      5)Leader通知Server1写入成功,Server1发送成功反馈给客户端
     * -----集群环境搭建-----
     * 1.创建集群:可单独每台配置,也可搭建一台Zookeeper服务器,再克隆
     * 2.配置id值:在dataDir指定目录下创建一个myid的文件并配置
     * 3.配置集群ip:在配置文件中配置 如server.A=B:C:D
     *      A)服务端id值
     *      B)服务端ip地址
     *      C)主从节点间通信端口
     *      D)重新选举时节点间通信端口
     * 4.依次修改每台服务器的ip和myid
     * 5.集群启动Zookeeper
     *      1)关闭防火墙,禁止自启动,查看状态: systemctl stop firewalld.service,systemctl disable firewalld,systemctl status firewalld
     *      2)bin目录下启动Zookeeper: ./zkServer.sh start
     *      3)bin目录下查看状态: ./zkServer.sh status
     * -----客户端命令-----
     * 1.启动客户端: ./zkCli.sh
     * 2.查看节点下的内容: ls [-s] 路径
     * 3.创建节点: create /mynode
     * 4.创建节点并保存数据: create /mynode 'mydata'
     * 5.获取节点中的数据: get /mynode
     * 6.创建临时节点: create -e /mynode 客户端断开连接则删除
     * 7.创建带序号的节点: create -s /mynode(后面会自动追加编号)
     * 8.修改节点中的数据: set /mynode 'mynewdata'
     * 9.删除节点: delete /mynode
     * 10.删除节点及子节点: deleteall /mynode
     * 11.监听节点: addWatch /mynode
     * --------------------面试题-----------------------
     * 1.持久化机制有哪些？
     *      1.内存快照文件：每隔一段时间对内存做全量的记录
     *      2.事务日志文件：日志文件增量记录
     * 2.什么是Zookeeper的通知
     *      1）客户端向服务端注册一个watch监听
     *      2）客户端保存watch至本地watchManager中
     *      3）服务端watch事件触发后，客户端收到通知后，从watchManager中取出对应的事件作处理
     * 3.写数据原理
     *      1)客户端向Server1上发送写数据请求
     *      2)若Server1不是Leader,则将请求转发给Leader
     *      3)Leader将请求发送给各Server,各Server写成功后将通知Leader
     *      4)当Leader收到半数以上写成功通知,则写入成功
     *      5)Leader通知Server1写入成功,Server1发送成功反馈给客户端
     *
     */

    private String connections = "192.168.194.100:2181," +
            "192.168.194.101:2181," +
            "192.168.194.102:2181";

    private int timeOut = 60 * 1000;

    private ZooKeeper zooKeeperClient;

    public void init() throws IOException {
        zooKeeperClient = new ZooKeeper(connections, timeOut, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("监听到变化");
            }
        });
    }

    public void createNode() throws InterruptedException, KeeperException {
        String path = zooKeeperClient.create("/mynode", "这是新节点的数据".getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        System.out.println(path);
    }

    public void selectNodeData() throws InterruptedException, KeeperException {
        byte[] data = zooKeeperClient.getData("/mynode", false, new Stat());
        System.out.println(new String(data));
    }

    public void updateNodeData() throws InterruptedException, KeeperException {
        Stat stat = zooKeeperClient.setData("/mynode", "这是节点的新数据".getBytes(), 0);
        System.out.println(stat);
    }

    public void deleteNode() throws InterruptedException, KeeperException {
        zooKeeperClient.delete("/mynode",1);
    }

    public void isNodeExisted() throws InterruptedException, KeeperException {
        Stat stat = zooKeeperClient.exists("/mynode", false);
        System.out.println(stat);
    }

    public void getNodeChildren() throws InterruptedException, KeeperException {
        List<String> childrenNode = zooKeeperClient.getChildren("/mynode", false);
        for (String node: childrenNode){
            System.out.println(node);
        }
    }
}
